Storytelling with Data

🌐 Humanitarian Aid Workers: Stepping into Crisis with Courage

Humanitarian aid workers often operate in some of the most dangerous parts of the world, providing critical support to people affected by conflict, political unrest, and natural disasters. They deliver food and medicine in war zones, build shelters in the aftermath of earthquakes, and offer life-saving assistance to displaced populations. Yet in these high-risk environments, aid workers themselves face constant threats to their safety—from stray bullets and collapsing infrastructure to targeted attacks by armed groups.

Aid workers participate in “I am #NotATarget” campaign while distributing food in Tukaraq, Somalia.

Source: The New Humanitarian

⏳ A Brief History of Modern Humanitarianism

The roots of modern humanitarian aid trace back to the mid-19th century, when Swiss businessman Henry Dunant witnessed the aftermath of the Battle of Solferino in 1859. His efforts to organize emergency relief for wounded soldiers led to the founding of the International Red Cross and the creation of the Geneva Conventions, which enshrined the principle of protecting civilians and aid workers during conflict.

Throughout the 20th century, as the world was shaken by two World Wars, proxy conflicts during the Cold War, and decolonization in Africa and Asia, the humanitarian community expanded into a global network. Organizations such as the United Nations, the International Committee of the Red Cross, Doctors Without Borders, and the World Food Programme became the backbone of modern aid delivery, bringing relief to some of the most remote and volatile places on Earth.

In the 21st century, the humanitarian landscape has become even more complex. New threats—terrorism, civil wars, political interference, and climate-related disasters—have made aid work increasingly difficult and dangerous.

💡 Unsung Heroes: The Spirit of Humanitarianism

Despite the risks, thousands of aid workers continue to serve on the frontlines of suffering. Their mission is not just about delivering supplies, but about preserving hope and defending human dignity.

They uphold the principles of neutrality, independence, and impartiality, even in the midst of political chaos.

They endure long deployments, harsh living conditions, and immense emotional and physical strain.

Often working in information-scarce, highly volatile, and insecure environments, they risk their lives to ensure help reaches those in need.

They are the doctors setting up mobile clinics under shellfire, the drivers navigating minefields to deliver food, the coordinators who return to the field even after being kidnapped. Quietly, yet courageously, they save lives—day after day.

📌 This Visualization Project is a Tribute

Through this data visualization project, we aim to shed light on the realities that aid workers facearound the world. By analyzing nearly three decades of security incident data, we hope to provide clearer insights for policy-makers, humanitarian organizations, and the public.

More than just a data story, this project is a quiet tribute—to the courage, sacrifice, and humanity of aid workers everywhere.

🌍 Dangerous Countries Overview

We uses data visualization to provide a comprehensive analysis of the most dangerous countries from 1997 to 2025, highlighting the security challenges aid workers face and how these risks vary across space and time.

📊 Top 10 Countries by Total Incidents

We begin with a broad overview by identifying the top 10 countries with the highest number of recorded security incidents involving aid workers.

Show code
import pandas as pd
import plotly.express as px

# Load data
df = pd.read_csv('data/security_incidents.csv')
df['Country'] = df['Country'].fillna('Unknown')

# Number of incidents by country
country_counts = df['Country'].value_counts().head(10).reset_index()
country_counts.columns = ['Country', 'Event Count']

# Interactive bar chart
fig = px.bar(country_counts, 
             x='Country', y='Event Count', 
             title='Top 10 Countries by Total Incidents',
             text='Event Count')

fig.update_traces(marker_color='crimson', textposition='outside')
fig.update_layout(xaxis_tickangle=-45, height=500)

fig.show()

From the bar chart, we can see that countries like Afghanistan, South Sudan, and Syria have consistently been high-risk zones for humanitarian operations. These countries have experienced a large number of attacks, kidnappings, and killings targeting aid personnel, underscoring the highly unstable security environment in these regions.

🗺️ Global Incident Heat Map

To provide a clearer picture of the geographical distribution of incidents, we created a global heat map that displays the location of each recorded event.

Show code
# Remove data without longitude and latitude
df_geo = df[['Country', 'Latitude', 'Longitude']].dropna()

# Geographic scatter plot
fig = px.scatter_geo(df_geo, 
                     lat='Latitude', lon='Longitude',
                     hover_name='Country',
                     title='Incident Locations Worldwide (1997–2025)',
                     opacity=0.6)

# Set layout
fig.update_traces(marker=dict(color='red', size=4))
fig.update_layout(geo=dict(projection_type='natural earth'), height=600)
fig.show()

The red clusters on the map are densely concentrated in specific regions, especially across the Middle East, Sub-Saharan Africa, and parts of Asia. This spatial analysis helps aid organizations better understand the regional patterns of insecurity, allowing them to optimize routes, select safer project locations, and plan staff deployment more effectively.

🔥 Where Aid Workers Have Faced the Highest Risk in Recent Years

In recent years, global humanitarian operations have faced unprecedented levels of violence, with certain countries emerging as epicenters of repeated and targeted attacks on aid personnel.

This section dives into temporal and categorical patterns of risk—examining which countries have seen the most frequent incidents, and how the nature of those threats has evolved.

📈 Shifting Hotspots: Year-by-Year Leaders in Aid Worker Incidents

Show code
import plotly.graph_objects as go

df = df.dropna(subset=['Year', 'Country'])
df['Year'] = df['Year'].astype(int)
years = sorted(df['Year'].unique())
red_gradient = ['#8B0000', '#B22222', '#DC143C', '#FA8072', '#FFA07A'] 


frames = []
for year in years:
    df_y = df[df['Year'] == year]
    top5 = df_y['Country'].value_counts().head(5)
    top5_sorted = top5.sort_values(ascending=False)
    ymax = top5_sorted.max() * 1.2
    frames.append((year, top5_sorted, ymax))


init_year, init_data, init_ymax = frames[0]
colors_init = red_gradient[:len(init_data)]

fig = go.Figure()
fig.add_trace(go.Bar(
    x=init_data.index,
    y=init_data.values,
    marker_color=colors_init
))

fig.update_layout(
    title=f"Top 5 Countries by Incidents in {init_year}",
    xaxis_title="Country",
    yaxis_title="Number of Incidents",
    yaxis=dict(range=[0, init_ymax]),
    template='plotly_white',
    font=dict(size=14),
    updatemenus=[dict(type="buttons",
                      buttons=[dict(label="Play",
                                    method="animate",
                                    args=[None, {"frame": {"duration": 800, "redraw": True},
                                                 "fromcurrent": True}]),
                               dict(label="Pause",
                                    method="animate",
                                    args=[[None], {"frame": {"duration": 0, "redraw": False},
                                                   "mode": "immediate",
                                                   "transition": {"duration": 0}}])],
                      direction="left", x=0.1, xanchor="left", y=1.2, yanchor="top")]
)

fig.frames = [
    go.Frame(data=[go.Bar(x=data.index, y=data.values, 
                          marker_color=red_gradient[:len(data)])],
             name=str(year),
             layout=go.Layout(title_text=f"Top 5 Countries by Incidents in {year}",
                              yaxis=dict(range=[0, ymax])))
    for year, data, ymax in frames
]

fig.update_layout(
    sliders=[dict(
        steps=[dict(method="animate",
                    args=[[str(year)],
                          {"frame": {"duration": 800, "redraw": True},
                           "mode": "immediate"}],
                    label=str(year)) for year, _, _ in frames],
        transition={"duration": 0},
        x=0.1, xanchor="left", y=0, yanchor="top",
        currentvalue=dict(font=dict(size=16), prefix="Year: ", visible=True, xanchor="right"),
        len=0.9
    )]
)

fig.show()

The animated bar chart above highlights the Top 5 most dangerous countries for aid workers by year, from 1997 to 2025. What makes this visualization powerful is its ability to trace how conflict zones evolve: some countries remain persistently high-risk over the years (like Afghanistan or South Sudan), while others emerge more recently as new hotspots due to political turmoil, civil unrest, or rapid militarization.

🎯 Deep Dive: The Three Most Dangerous Countries (2023–2025)

To better understand the specific types of violence aid workers are exposed to, we zoom in on the three countries with the highest number of incidents between 2023 and 2025.

🟥 South Sudan: Aid Amid Fragile Nation-Building

South Sudan officially gained independence in 2011, raising hopes for peace and reconstruction. However, just two years later, in late 2013, a violent power struggle erupted between President Salva Kiir and former Vice President Riek Machar, sparking a brutal civil war and deepening ethnic divisions.

South Sudanese volunteers carrying relief supplies

Source: Top Africa News

The threats facing aid workers in South Sudan are rooted in the country’s political fragility and fractured security environment:

  1. Numerous non-state armed groups operate beyond government control, and the state lacks authority over large rural areas and transport routes;

  2. Humanitarian goods are often seen as “resources of war,” and aid workers are frequently targeted for looting, leverage, or political coercion;

  3. In some regions, a “post-attack aid cycle” emerges—armed groups ambush aid missions and then lie in wait for future relief deliveries;

  4. Infrastructure is extremely weak, with poor roads and limited communication, making emergency response vulnerable to isolation.

Despite international efforts to support peace, conflict remains sporadic, and local staff—who make up the majority of humanitarian personnel—bear a disproportionate share of the violence and emotional toll. Humanitarian work in South Sudan today faces the dual challenge of high risk and low visibility.

🟤 Sudan: A Country in Transition—and in Turmoil

Following the ousting of long-time ruler Omar al-Bashir in 2019, Sudan appeared poised for democratic transition. Yet, the fragile power-sharing agreement between civilian and military actors quickly unraveled, culminating in a devastating civil conflict between the Sudanese Armed Forces (SAF) and the Rapid Support Forces (RSF) in 2023.

International aid workers walking side by side

Source: European Commission

For aid workers, Sudan now represents a multi-layered risk environment:

  1. Major cities—including the capital, Khartoum—have become battle zones, meaning even historically “safe” urban areas are under fire;

  2. Mass displacement has surged across borders, but many crossings are under contested control;

  3. Humanitarian warehouses and offices have been looted, and some repurposed as barracks or weapons storage;

  4. Both SAF and RSF have been reported to obstruct humanitarian access and intimidate aid personnel.

The danger in Sudan lies not only in the scale of conflict, but in its complexity: a fractured society, foreign interference, and growing skepticism about the neutrality of humanitarian organizations. Many international NGOs have been forced to withdraw or operate remotely, leaving only limited in-country capacity to respond.

🟢 Occupied Palestinian Territories: Humanitarian Work Under Siege

The Occupied Palestinian Territories—especially the Gaza Strip—exist under conditions of intense military pressure, frequent armed clashes, and severe humanitarian need. Recurring conflict between Israeli forces and Hamas has turned humanitarian aid into a highly politicized and operationally constrained effort.

Aid workers standing in front of medical supplies

Source: MAP

Aid workers in the region face a unique set of challenges:

  1. Aerial bombardment is the most significant threat, and workers often lack adequate shelter or early-warning systems;

  2. Aid convoys and medical facilities have been repeatedly struck—whether by accident or not — with even ambulances and hospitals at risk;

  3. Ground incursions in areas like Gaza City and refugee camps result in intense urban combat, with no guaranteed humanitarian corridors;

  4. Tight border and movement restrictions severely limit deployment, rotation, or evacuation of international staff.

While organizations like the UN maintain long-term operations in Gaza and the West Bank, the physical overlap between aid missions and military targets creates constant danger. Humanitarian actors also face pressure on their credibility, neutrality, and legal standing in such a contested space.

🔍 Interpreting the Data Behind the Charts (2023–2025)

The pie chart visualizations provide a multi-dimensional view of the threats faced by aid workers in recent years. To make sense of the data, we now take a closer look at the three most dangerous countries between 2023 and 2025: South Sudan, Sudan, and the Occupied Palestinian Territories. For each, we examine their political context, analyze the charted statistics, and offer tailored recommendations for aid organizations working on the ground.

Through a set of toggleable pie charts, we explore:

Means of Attack – How were workers attacked? (e.g., shootings, kidnappings, explosives)

Attack Context – Under what conditions did the attacks occur? (e.g., ambush, crossfire, individual targeting)

Location of Incidents – Where did these attacks happen? (e.g., roads, compounds, public spaces)

Show code
from plotly.subplots import make_subplots
# Screening 2023-2025 events
df_recent = df[df['Year'].isin([2023, 2025])]

# Find the top three countries with the most incidents
top3_countries = df_recent['Country'].value_counts().head(3).index.tolist()
print("Top 3 Countries (2023-2025):", top3_countries)

# Color Mapping

means_color_map = {
    'Complex attack': '#A9A9A9',
    'Shooting': '#DC143C',
    'Unknown': '#808080',
    'Shelling': '#FFA500',
    'Bodily assault': '#8B4513',
    'Other Explosives': '#00CED1',
    'Kidnapping': '#FFD700',
    'Kidnap-killing': '#9932CC',
    'Aerial bombardment': '#228B22'
}

context_color_map = {
    'Combat/Crossfire': '#00FFFF',
    'Individual attack': '#FFC0CB',
    'Raid': '#800080',
    'Ambush': '#FF8C00',
    'Unknown': '#C0C0C0',
    'Detention': '#00FF00'
}

location_color_map = {
    'Project site': '#D2691E',
    'Road': '#008080',
    'Unknown': '#8B0000',
    'Public location': '#6A5ACD',
    'Home': '#00008B',
    'Office/compound': '#808000',
    'Custody': '#B22222'
}

# Create a chart
fig = make_subplots(rows=1, cols=3, specs=[[{'type':'domain'}]*3],
                    subplot_titles=[f"{c}" for c in top3_countries])

for i, country in enumerate(top3_countries):
    df_c = df_recent[df_recent['Country'] == country]

    # 1. Means of attack
    counts = df_c['Means of attack'].fillna('Unknown').value_counts()
    labels = counts.index
    colors = [means_color_map.get(label, '#D3D3D3') for label in labels]
    fig.add_trace(go.Pie(labels=labels, values=counts.values, name=country,
                         marker=dict(colors=colors), visible=True if i == 0 else True),
                  row=1, col=i+1)

for i, country in enumerate(top3_countries):
    df_c = df_recent[df_recent['Country'] == country]

    counts = df_c['Attack context'].fillna('Unknown').value_counts()
    labels = counts.index
    colors = [context_color_map.get(label, '#D3D3D3') for label in labels]
    fig.add_trace(go.Pie(labels=labels, values=counts.values, name=country,
                         marker=dict(colors=colors), visible=False),
                  row=1, col=i+1)

for i, country in enumerate(top3_countries):
    df_c = df_recent[df_recent['Country'] == country]

    counts = df_c['Location'].fillna('Unknown').value_counts()
    labels = counts.index
    colors = [location_color_map.get(label, '#D3D3D3') for label in labels]
    fig.add_trace(go.Pie(labels=labels, values=counts.values, name=country,
                         marker=dict(colors=colors), visible=False),
                  row=1, col=i+1)

total_traces = 9
group_count = 3 
visibility = []

for g in range(group_count): 
    vis = []
    for i in range(total_traces):
        vis.append(g * 3 <= i < (g + 1) * 3)
    visibility.append(vis)

fig.update_layout(
    updatemenus=[dict(
        active=0,
        buttons=[
            dict(label="Means of Attack",
                 method="update",
                 args=[{"visible": visibility[0]},
                       {"title": "Means of Attack"}]),
            dict(label="Attack Context",
                 method="update",
                 args=[{"visible": visibility[1]},
                       {"title": "Attack Context"}]),
            dict(label="Location",
                 method="update",
                 args=[{"visible": visibility[2]},
                       {"title": "Attack Location"}])
        ],
        direction="down",
        x=0.5,
        xanchor="center",
        y=1.2,
        yanchor="top"
    )],
    height=600,
    title_text="Means of Attack"
)

fig.show()
Top 3 Countries (2023-2025): ['South Sudan', 'Sudan', 'Occupied Palestinian Territories']